
实现调用约定的规则是，将LLVM中间表示(IR)降为机器码的重要组成部分，基本规则可以在目标描述中定义。

大多数调用约定遵循一个基本模式:定义一个寄存器子集用于参数传递。若这个子集没有耗尽，则在下一个空闲寄存器中传递下一个参数。若没有空闲的寄存器，则将值传递到堆栈上。这可以通过循环遍历参数，决定如何将每个参数传递给被调用函数来实现，同时跟踪使用的寄存器。LLVM中，这个循环是在框架内实现的，状态保存在一个名为CCState的类中，所以可在目标描述中定义规则。

这些规则是作为一系列条件给出的。若条件成立，则执行一个操作。根据该操作的结果，要么为参数找到一个位置，要么计算下一个条件。例如，32位整数在寄存器中传递。条件是类型检查，动作是将寄存器赋值给该参数。目标描述如下所示:

\begin{shell}
CCIfType<[i32],
        CCAssignToReg<[R2, R3, R4, R5, R6, R7, R8, R9]>>,
\end{shell}

当然，若调用的函数有超过8个参数，寄存器就会耗尽，操作就会失败。剩下的参数在堆栈上传递，可以将其指定为下一个动作:

\begin{shell}
CCAssignToStack<4, 4>
\end{shell}

第一个参数是以字节为单位的堆栈槽的大小，而第二个参数是对齐方式。它是一个包罗万象的规则，所以没有任何限制条件。

\mySubsubsection{12.1.1.}{执行调用约定规则}

对于调用约定，需要注意更多预定义的条件和操作。例如，CCIfInReg检查参数是否标记为inreg属性，若函数具有可变参数列表，CCIfVarArg计算结果为true。CCPromoteToType动作将参数的类型提升为更大的类型，CCPassIndirect动作表明参数值应该存储在堆栈中，并且指向该存储的指针作为普通参数传递。所有预定义的条件和动作都可以在llvm/include/llvm/Target/TargetCallingConv.td中引用。

参数和返回值都以这种方式定义，将把定义放入M88kCallingConv.td文件中:

\begin{enumerate}
\item
首先，必须为参数定义规则。为了简化编码，只考虑32位值:

\begin{cpp}
def CC_M88k : CallingConv<[
    CCIfType<[i8, i16], CCPromoteToType<i32>>,
    CCIfType<[i32,f32],
        CCAssignToReg<[R2, R3, R4, R5, R6, R7, R8, R9]>>,
    CCAssignToStack<4, 4>
]>;
\end{cpp}

\item
之后，必须定义返回值的规则:

\begin{cpp}
def RetCC_M88k : CallingConv<[
    CCIfType<[i32], CCAssignToReg<[R2]>>
]>;
\end{cpp}

\item
最后，必须定义调用者保存的寄存器序列。使用序列操作符来生成寄存器序列，而不是将它们写下来:

\begin{cpp}
def CSR_M88k :
    CalleeSavedRegs<(add R1, R30,
        (sequence "R%d", 25, 14))>;
\end{cpp}
\end{enumerate}

目标描述中为调用约定定义规则的好处是，可以使用各种指令选择方法重用。接下来，通过选择DAG查看指令的选择。




